﻿//-------------------------------------------------------------------------------
// <copyright file="FBObject.cs" company="TaskyMedia LLC">
//     Copyright (c) TaskyMedia LLC.
// </copyright>
//-------------------------------------------------------------------------------

/*
 * Copyright (c) 2010, TaskyMedia LLC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *   Redistributions of source code must retain the above copyright notice, this 
 *     list of conditions and the following disclaimer.
 *   Redistributions in binary form must reproduce the above copyright notice, this
 *     list of conditions and the following disclaimer in the documentation and/or
 *     other materials provided with the distribution.
 *   Neither the name of TaskyMedia LLC nor the names of its contributors may be used
 *   to endorse or promote products derived from this software without specific prior
 *   written permission.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE. 
 */

namespace FBGraphApi
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using Newtonsoft.Json.Linq;

    /// <summary>
    /// Represents an object in the Facebook Graph. Provides functionality
    /// to parse JSON and fill the object's fields.
    /// </summary>
    public abstract class FBObject
    {
        /// <summary>
        /// Contains a dictionary of the object field names and the corresponding properties.
        /// Used to cache reflection data.
        /// </summary>
        private Dictionary<string, PropertyInfo> _objectFields;

        /// <summary>
        /// Provides synchronization locking for the reflection dictionary.
        /// </summary>
        private object _objectFieldsLockObj = new object();

        /// <summary>
        /// Initializes a new instance of the <see cref="FBObject"/> class.
        /// </summary>
        public FBObject()
        {
            this.BuildReflectionTable();
        }

        /// <summary>
        /// Fills the object's fields with the JSON result.
        /// </summary>
        /// <param name="jsonData">The JSON data.</param>
        internal virtual void FillObject(string jsonData)
        {
            JObject jsonObject = JObject.Parse(jsonData);
            foreach (JProperty token in jsonObject.Children())
            {
                PropertyInfo pi = this.GetFieldPropertyInfo(token.Name);
                if (pi != null)
                {
                    if (pi.PropertyType == typeof(string))
                    {
                        string value = jsonObject[token.Name].Value<string>();
                        pi.SetValue(this, value, null);
                    }
                    else if (pi.PropertyType == typeof(DateTime))
                    {
                        DateTime dateVal = jsonObject[token.Name].Value<DateTime>();
                        pi.SetValue(this, dateVal, null);
                    }
                    else if (pi.PropertyType == typeof(int))
                    {
                        int numVal = jsonObject[token.Name].Value<int>();
                        pi.SetValue(this, numVal, null);
                    }
                    else if (pi.PropertyType == typeof(FBIdNameObject))
                    {
                        string tokenJson = token.Value.ToString();
                        FBIdNameObject val = new FBIdNameObject();
                        val.FillObject(tokenJson);
                        pi.SetValue(this, val, null);
                    }
                }
            }
        }

        /// <summary>
        /// Determines whether the current object contains a field.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <returns>
        /// <c>true</c> if the object contains the specified field; otherwise, <c>false</c>.
        /// </returns>
        protected bool ContainsField(string field)
        {
            return this._objectFields.ContainsKey(field);
        }

        /// <summary>
        /// Gets the field property info.
        /// </summary>
        /// <param name="field">The name of the field.</param>
        /// <returns>Returns a <see cref="System.Reflection.PropertyInfo" /> for the specified
        /// field, if found; otherwise, returns <c>null</c>.</returns>
        private PropertyInfo GetFieldPropertyInfo(string field)
        {
            PropertyInfo result = null;
            this._objectFields.TryGetValue(field, out result);
            return result;
        }

        /// <summary>
        /// Builds the object field reflection table.
        /// </summary>
        private void BuildReflectionTable()
        {
            if (this._objectFields == null)
            {
                lock (this._objectFieldsLockObj)
                {
                    if (this._objectFields == null)
                    {
                        this._objectFields = new Dictionary<string, PropertyInfo>();
                        Type objectType = this.GetType();
                        foreach (PropertyInfo propertyInfo in objectType.GetProperties())
                        {
                            // Get the attributes
                            ObjectFieldAttribute[] attribs = (ObjectFieldAttribute[])propertyInfo.GetCustomAttributes(typeof(ObjectFieldAttribute), false);

                            if (attribs.Length > 0)
                            {
                                this._objectFields.Add(attribs[0].FieldName, propertyInfo);
                            }
                        }
                    }
                }
            }
        }
    }
}
